﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace PloobsEngine.SceneControl
{    

    public class HdrPostEffect : IPostEffect
    {
        System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
        long lastTime = 0;
        float dt = 0;        
        EngineStuff engine;
        Effect effect ;
        Effect tone;
        Effect toScreen;
        Effect fULLGPUBlur;
        Effect luminance;
        Effect threshold;        
        RenderTarget2D[] luminanceChain;
        protected RenderTarget2D currentFrameLuminance;
        protected RenderTarget2D currentFrameAdaptedLuminance;
        protected RenderTarget2D lastFrameAdaptedLuminance;
        protected float toneMapKey = 0.8f;
        protected float maxLuminance = 512.0f;
        protected float bloomThreshold = 0.85f;
        protected float bloomMultiplier = 1.0f;
        protected float blurSigma = 2.5f;
        protected TexturesPool intermediateTextures = new TexturesPool();
        

        private Texture2D DownScale(IRenderHelper rHelper, Texture2D input, ref RenderTarget2D downscaleTarget,String technichName)
        {            
            effect.CurrentTechnique = effect.Techniques[technichName];
            effect.Parameters["SourceTexture0"].SetValue(rHelper[PrincipalConstants.CurrentImage]);
            effect.Parameters["g_vSourceDimensions"].SetValue(new Vector2(input.Width, input.Height));
            effect.Parameters["g_vDestinationDimensions"].SetValue(new Vector2(downscaleTarget.Width, downscaleTarget.Height));
            rHelper.SetUpRenderTarget(downscaleTarget);
            rHelper.RenderTextureToFullScreenVertexPixel(effect);
            return rHelper.GetResultsRenderTarget();            
        }

        private Texture2D DownScale(IRenderHelper rHelper,Texture2D input, int factor,SurfaceFormat format, String technicName)
        {
            IntermediateTexture downscaleTarget = intermediateTextures.GetIntermediateTexture(input.Width / factor, input.Height / factor, format);            
            Texture2D tex = this.DownScale(rHelper,input,ref downscaleTarget.RenderTarget,technicName);
            downscaleTarget.InUse = false;
            return tex;
        }

        private Texture2D Luminance(IRenderHelper rHelper, Texture2D input,RenderTarget2D target,String techniqueName)
        {
            luminance.CurrentTechnique = luminance.Techniques[techniqueName];
            luminance.Parameters["SourceTexture0"].SetValue(input);
            luminance.Parameters["g_vSourceDimensions"].SetValue(new Vector2(input.Width, input.Height));
            luminance.Parameters["g_vDestinationDimensions"].SetValue(new Vector2(target.Width, target.Height));
            rHelper.SetUpRenderTarget(target);
            rHelper.RenderTextureToFullScreenVertexPixel(luminance);
            return rHelper.GetResultsRenderTarget();            
            
        }
        private Texture2D BlurHV(IRenderHelper rHelper, Texture2D input, RenderTarget2D target)
        {            
            String baseTechniqueName = "GaussianBlur";

            // Do horizontal pass first
            fULLGPUBlur.CurrentTechnique = fULLGPUBlur.Techniques[baseTechniqueName + "H"];
            fULLGPUBlur.Parameters["g_fSigma"].SetValue(blurSigma);
            fULLGPUBlur.Parameters["SourceTexture0"].SetValue(input);
            fULLGPUBlur.Parameters["g_vSourceDimensions"].SetValue(new Vector2(input.Width, input.Height));
            fULLGPUBlur.Parameters["g_vDestinationDimensions"].SetValue(new Vector2(target.Width, target.Height));
            rHelper.SetUpRenderTarget(target);
            rHelper.RenderTextureToFullScreenVertexPixel(fULLGPUBlur);
            Texture2D pass1 = rHelper.GetResultsRenderTarget();

            // Now the vertical pass 
            IntermediateTexture t2 = intermediateTextures.GetIntermediateTexture(target.Width, target.Height, SurfaceFormat.Color);                        
            fULLGPUBlur.CurrentTechnique = fULLGPUBlur.Techniques[baseTechniqueName + "V"];
            fULLGPUBlur.Parameters["SourceTexture0"].SetValue(pass1);
            rHelper.SetUpRenderTarget(t2.RenderTarget);
            rHelper.RenderTextureToFullScreenVertexPixel(fULLGPUBlur);
            Texture2D tex =  rHelper.GetResultsRenderTarget();
            t2.InUse = false;
            return tex;

        }


        public void Draw(IRenderHelper rHelper,  Microsoft.Xna.Framework.GameTime gt, Microsoft.Xna.Framework.Rectangle screenRec, IWorld world)
        {
                long time = stopwatch.ElapsedMilliseconds;
                dt = (time - lastTime) / 1000.0f;
                lastTime = time;
                           
               Texture2D d4 = DownScale(rHelper, rHelper[PrincipalConstants.CurrentImage], 4, SurfaceFormat.HalfVector4, "Downscale4");               
               Texture2D d16 = DownScale(rHelper, d4, 4, SurfaceFormat.HalfVector4,"Downscale4");
               //d16.Save("tt.png", ImageFileFormat.Png);
               Luminance(rHelper, d16, luminanceChain[0], "Luminance");
               //luminanceChain[0].GetTexture().Save("bla.png", ImageFileFormat.Png);
               
               for (int i = 1; i < luminanceChain.Length; i++)
                   DownScale(rHelper, luminanceChain[i - 1].GetTexture(), ref luminanceChain[i], "Downscale4");   ///possivelmente mudar para o efeito Downscale4Luminance

               // Final downscale                           
               DownScale(rHelper, luminanceChain[luminanceChain.Length - 1].GetTexture(), ref currentFrameLuminance, "Downscale4Luminance");
               
               // Adapt the luminance, to simulate slowly adjust exposure
               luminance.Parameters["g_fDT"].SetValue(dt);               
               luminance.Parameters["SourceTexture1"].SetValue(lastFrameAdaptedLuminance.GetTexture());
               Luminance(rHelper, currentFrameLuminance.GetTexture(), currentFrameAdaptedLuminance, "CalcAdaptedLuminance");
          

               ///Bloom
               IntermediateTexture thresholdTex = intermediateTextures.GetIntermediateTexture(d16.Width, d16.Height, SurfaceFormat.HalfVector4);
               threshold.CurrentTechnique = threshold.Techniques["Threshold"];
               threshold.Parameters["g_fThreshold"].SetValue(bloomThreshold);
               threshold.Parameters["g_fMiddleGrey"].SetValue(toneMapKey);
               threshold.Parameters["g_fMaxLuminance"].SetValue(maxLuminance);               
               threshold.Parameters["SourceTexture0"].SetValue(d16);
               threshold.Parameters["SourceTexture1"].SetValue(currentFrameAdaptedLuminance.GetTexture());
               threshold.Parameters["g_vSourceDimensions"].SetValue(new Vector2(d16.Width, d16.Height));
               threshold.Parameters["g_vDestinationDimensions"].SetValue(new Vector2(thresholdTex.RenderTarget.Width, thresholdTex.RenderTarget.Height));
               rHelper.SetUpRenderTarget(thresholdTex.RenderTarget);
               rHelper.RenderTextureToFullScreenVertexPixel(threshold);
               Texture2D blom = rHelper.GetResultsRenderTarget();

               IntermediateTexture postBlur = intermediateTextures.GetIntermediateTexture(d16.Width, d16.Height, SurfaceFormat.Color);
               Texture2D blur = BlurHV(rHelper, blom, postBlur.RenderTarget);
               thresholdTex.InUse = false;

               IntermediateTexture upscale1 = intermediateTextures.GetIntermediateTexture(EngineStuff.BackBufferWidth / 8, EngineStuff.BackBufferHeight / 8, SurfaceFormat.Color);
               DownScale(rHelper, postBlur.RenderTarget.GetTexture(), ref upscale1.RenderTarget, "ScaleHW");
               postBlur.InUse = false;

               IntermediateTexture upscale2 = intermediateTextures.GetIntermediateTexture(EngineStuff.BackBufferWidth / 4, EngineStuff.BackBufferHeight / 4, SurfaceFormat.Color);
               DownScale(rHelper, upscale1.RenderTarget.GetTexture(), ref upscale2.RenderTarget, "ScaleHW");
               upscale1.InUse = false;

               IntermediateTexture bloom = intermediateTextures.GetIntermediateTexture(EngineStuff.BackBufferWidth / 2, EngineStuff.BackBufferHeight / 2, SurfaceFormat.Color);
               Texture2D resp = DownScale(rHelper, upscale2.RenderTarget.GetTexture(), ref bloom.RenderTarget, "ScaleHW");
               upscale2.InUse = false;

               //toScreen.Parameters["SourceTexture0"].SetValue(resp);
               //toScreen.Parameters["g_vSourceDimensions"].SetValue(new Vector2(resp.Width, resp.Height));
               //toScreen.Parameters["g_vDestinationDimensions"].SetValue(new Vector2(EngineStuff.BackBufferWidth, EngineStuff.BackBufferHeight));
               //rHelper.RenderTextureToFullScreenVertexPixel(toScreen);


               // Now do tone mapping on the main source image, and add in the bloom
               tone.CurrentTechnique = tone.Techniques["ToneMap"];
               tone.Parameters["g_fMiddleGrey"].SetValue(toneMapKey);
               tone.Parameters["g_fMaxLuminance"].SetValue(maxLuminance);
               tone.Parameters["g_fBloomMultiplier"].SetValue(bloomMultiplier);               
               tone.Parameters["g_vDestinationDimensions"].SetValue(new Vector2(EngineStuff.BackBufferWidth, EngineStuff.BackBufferHeight));
               tone.Parameters["SourceTexture0"].SetValue(rHelper[PrincipalConstants.CurrentImage]);
               tone.Parameters["SourceTexture1"].SetValue(currentFrameAdaptedLuminance.GetTexture());
               tone.Parameters["SourceTexture2"].SetValue(resp);               
               rHelper.RenderTextureToFullScreenVertexPixel(tone);              
               
               //// Flip the luminance textures
               Swap(ref currentFrameAdaptedLuminance, ref lastFrameAdaptedLuminance);

               bloom.InUse = false;
        }

        public void init(EngineStuff engine)
        {
            effect = EngineStuff.InternalContentManager.GetAsset<Effect>("DownScaling");
            luminance = EngineStuff.InternalContentManager.GetAsset<Effect>("Luminance");
            threshold = EngineStuff.InternalContentManager.GetAsset<Effect>("Threshold");
            fULLGPUBlur = EngineStuff.InternalContentManager.GetAsset<Effect>("FULLGPUBlur");
            tone = EngineStuff.InternalContentManager.GetAsset<Effect>("ToneHdr");
            toScreen = EngineStuff.InternalContentManager.GetAsset<Effect>("CopyToScreen"); 
            this.engine = engine;

            int chainLength = 1;
            int startSize = (int)MathHelper.Min(EngineStuff.BackBufferWidth / 16, EngineStuff.BackBufferHeight / 16);
            int size = 16;
            for (size = 16; size < startSize; size *= 4)
                chainLength++;

            luminanceChain = new RenderTarget2D[chainLength];
            size /= 4;
            for (int i = 0; i < chainLength; i++)
            {
                luminanceChain[i] = new RenderTarget2D(EngineStuff.GraphicsDevice, size, size, 0, SurfaceFormat.Single);
                size /= 4;
            }


            currentFrameLuminance = new RenderTarget2D(EngineStuff.GraphicsDevice, 1, 1, 1, SurfaceFormat.Single, RenderTargetUsage.DiscardContents);
            currentFrameAdaptedLuminance = new RenderTarget2D(EngineStuff.GraphicsDevice, 1, 1, 1, SurfaceFormat.Single, RenderTargetUsage.DiscardContents);
            lastFrameAdaptedLuminance = new RenderTarget2D(EngineStuff.GraphicsDevice, 1, 1, 1, SurfaceFormat.Single, RenderTargetUsage.DiscardContents);
            RenderTarget last = EngineStuff.GraphicsDevice.GetRenderTarget(0);
            EngineStuff.GraphicsDevice.SetRenderTarget(0, lastFrameAdaptedLuminance);
            EngineStuff.GraphicsDevice.Clear(Color.White);
            EngineStuff.GraphicsDevice.SetRenderTarget(0, last as RenderTarget2D);

        }

        protected void Swap(ref RenderTarget2D rt1, ref RenderTarget2D rt2)
        {
            RenderTarget2D temp = rt1;
            rt1 = rt2;
            rt2 = temp;
        }


    }
}
